/*
 *  Copyright (c) 2012 Alaitsev I.K.
 *  Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 *  and associated documentation files (the "Software"), to deal in the Software without restriction,
 *  including without limitation the rights to use, copy, modify, merge, publish, distribute,
 *  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all copies or
 *  substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
 *  NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 *  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include "core.h"

#include <QDebug>

#include "common_defines_and_includes.h"
#include <QProcess>

Core::Core(QObject *parent) :
  QObject(parent) {
  _external_electrode_radius_no_grade = 0.;
  _internal_electrode_radius_no_grade = 0.;
  _offset_ie_rel_ee_no_grade          = 0.;
  _grade_offset                       = 0.;
  _grade_radius                       = 0.;

  _grid_steps_count_angle  = 100;
  _grid_steps_count_radial = 10;
}

void Core::update_system() {
}

void Core::start_processing() {
  _femm_process = new QProcess (this);

  _femm_process ->start (QString("./femm/femm.exe"),
                         QStringList({"-lua-script=physics-tb-femm-processor.lua",
                                      //"-windowhide",
                                      "-lua-var=internal_electrode_radius=" + QString::number (_internal_electrode_radius_no_grade * pow(10,_grade_radius) * 1000),
                                      "-lua-var=external_electrode_radius=" + QString::number (_external_electrode_radius_no_grade * pow(10,_grade_radius) * 1000),
                                      "-lua-var=offset="                    + QString::number (_offset_ie_rel_ee_no_grade * pow(10,_grade_offset) * 1000),
                                      "-lua-var=potential_difference=" + QString::number (_potential_difference_no_grade * pow (10, _grade_potential_difference)),
                                      "-lua-var=magnetic_induction="   + QString::number (_magnetic_induction_no_grade * pow (10, _grade_magnetic_induction)),
                                      "-lua-var=angle=" + QString::number (_angle)}),
                         QIODevice::ReadOnly);
  connect (_femm_process, SIGNAL (finished(int)),
           this,          SLOT   (on_processing_finished()));
}

void Core::on_processing_finished() {
}

/*
 *  Во всех функциях, устанавливающих значения полей, производится проверка допустимости новых
 *  значений.
 *  В случае, если на вход подано недопустимое значение, испускается сигнал с текущим значением
 *  поля, которое считается допустимым.
*/
double Core::internal_electrode_radius_no_grade(double in_new_value) {
  if (isfinite(in_new_value)) {
    double_t er  = _external_electrode_radius_no_grade * exp (M_LN10 * _grade_radius);
    double_t ir  = in_new_value * exp (M_LN10 * _grade_radius);
    double_t off = _offset_ie_rel_ee_no_grade * exp (M_LN10 * _grade_offset);
    if ((ir + off) < er) {
      _internal_electrode_radius_no_grade = in_new_value;
      _internal_electrode_radius          = ir;
    }
    else {
      emit internal_electrode_radius_valid_no_grade (_internal_electrode_radius_no_grade);
    }
    emit system_parameters_changed ();
  }
  else {
    emit internal_electrode_radius_valid_no_grade (_internal_electrode_radius_no_grade);
  }

  return _internal_electrode_radius_no_grade;
}

double Core::external_electrode_radius_no_grade (double in_new_value) {
  if (isfinite(in_new_value)) {
    double_t er  = in_new_value * exp (M_LN10 * _grade_radius);
    double_t ir  = _internal_electrode_radius_no_grade * exp (M_LN10 * _grade_radius);
    double_t off = _offset_ie_rel_ee_no_grade * exp (M_LN10 * _grade_offset);
    if ((ir + off) < er) {
      _external_electrode_radius_no_grade = in_new_value;
      _external_electrode_radius          = er;
    }
    else {
      emit external_electrode_radius_valid_no_grade (_external_electrode_radius_no_grade);
    }
    emit system_parameters_changed ();
  }
  else {
    emit external_electrode_radius_valid_no_grade (_external_electrode_radius_no_grade);
  }

  return _external_electrode_radius_no_grade;
}

double Core::offset_ie_rel_ee_no_grade (double in_new_value) {
  if (isfinite(in_new_value)) {
    double_t er  = _external_electrode_radius_no_grade * exp (M_LN10 * _grade_radius);
    double_t ir  = _internal_electrode_radius_no_grade * exp (M_LN10 * _grade_radius);
    double_t off = in_new_value * exp (M_LN10 * _grade_offset);
    if ((ir + off) < er) {
      _offset_ie_rel_ee_no_grade = in_new_value;
      _offset_ie_rel_ee          = off;
    }
    else {
      emit offset_ie_rel_ee_valid_no_grade (_offset_ie_rel_ee_no_grade);
    }
    emit system_parameters_changed ();
  }
  else {
    emit offset_ie_rel_ee_valid_no_grade (_offset_ie_rel_ee_no_grade);
  }

  return _offset_ie_rel_ee_no_grade;
}

double Core::grade_radius(double in_new_value) {
  if (isfinite(in_new_value)) {
    double_t er  = _external_electrode_radius_no_grade * exp (M_LN10 * in_new_value);
    double_t ir  = _internal_electrode_radius_no_grade * exp (M_LN10 * in_new_value);
    double_t off = _offset_ie_rel_ee_no_grade * exp (M_LN10 * _grade_offset);
    if ((ir + off) < er) {
      _grade_radius = in_new_value;
    }
    else {
      emit grade_radius_valid(_grade_radius);
    }
    emit system_parameters_changed ();
  }
  else {
    emit grade_radius_valid(_grade_radius);
  }

  return _grade_radius;
}

double Core::grade_offset(double in_new_value) {
  if (isfinite(in_new_value)) {
    double_t er  = _external_electrode_radius_no_grade * exp (M_LN10 * _grade_radius);
    double_t ir  = _internal_electrode_radius_no_grade * exp (M_LN10 * _grade_radius);
    double_t off = _offset_ie_rel_ee_no_grade * exp (M_LN10 * in_new_value);
    if ((ir + off) < er) {
      _grade_offset = in_new_value;
    }
    else {
      emit grade_offset_valid(_grade_offset);
    }
    emit system_parameters_changed ();
  }
  else {
    emit grade_offset_valid(_grade_offset);
  }

  return _grade_radius;
}

double Core::potential_difference_no_grade(double in_new_value) {
  if (isfinite(in_new_value)) {
    _potential_difference_no_grade = in_new_value;
    emit system_parameters_changed ();
  }
  else {
    emit potential_difference_valid_no_grade(_potential_difference_no_grade);
  }

  return _potential_difference_no_grade;
}

double Core::magnetic_induction_no_grade(double in_new_value) {
  if (isfinite(in_new_value)) {
    _magnetic_induction_no_grade = in_new_value;
    emit system_parameters_changed ();
  }
  else {
    emit magnetic_induction_valid_no_grade(_magnetic_induction_no_grade);
  }

  return _magnetic_induction_no_grade;
}

double Core::grade_potential_difference(double in_new_value) {
  if (isfinite(in_new_value)) {
    _grade_potential_difference = in_new_value;
    emit system_parameters_changed ();
  }
  else {
    emit grade_potential_difference_valid(_grade_potential_difference);
  }

  return _grade_potential_difference;
}

double Core::grade_magnetic_induction(double in_new_value) {
  if (isfinite(in_new_value)) {
    _grade_magnetic_induction = in_new_value;
    emit system_parameters_changed ();
  }
  else {
    emit grade_offset_valid(_grade_magnetic_induction);
  }

  return _grade_magnetic_induction;
}

Core::core_eqp_l_array Core::equipotential_lines (int in_steps_radius,
                                                  int in_steps_angle) {
  core_eqp_l_array res;

  res .resize (in_steps_radius + 1);

  for (int i = 0;
       i < res .size();
       ++i) {
    res [i] .resize(in_steps_angle+1);
    double_t Ra  = _external_electrode_radius_no_grade;
    double_t rk  = _internal_electrode_radius_no_grade;
    double_t ro = _offset_ie_rel_ee_no_grade * exp (M_LN10 * (_grade_offset - _grade_radius));
    double k = double(i)/in_steps_radius;
    for (int j = 0;
         j < res [i] .size();
         ++j) {
      /* Всё это только временная версия, т.к. основано на предсказании погоды на луне. */
      double fi = 2.*M_PI * j / in_steps_angle;
      /*
       *  NOTE:
       *    Рассчёт радиуса эквипотенциали.
      */
      double r = (exp (k*log ((sqrt (Ra*Ra - (ro*sin(fi))*(ro*sin(fi))) - ro * cos(fi))/rk) + log(rk)) - rk);
      res [i][j] = QPointF (fi,r);
    }
  }
  return res;
}

Core::lines_pack_cylinder Core::equipotential_lines_cylinder_coordinates_analitic_solution(int in_steps_radius,
                                                                                           int in_steps_angle) {
  core_eqp_l_array eqp_lines = equipotential_lines (in_steps_radius,
                                                    in_steps_angle);
  lines_pack_cylinder res;

  double max_y = -1;
  double max_x = -1;
  double min_y = 1;
  double min_x = 1;

//  res ._l ._xs .resize(in_steps_radius + 1);
//  res ._l ._ys .resize(in_steps_angle + 1);

  int i = 0;

  for (auto arr : eqp_lines) {
    int j = 0;
    lines_cylinder line;
    QVector <double>& x = line ._xs;
    QVector <double>& y = line ._ys;
    QVector <double>& f = line ._fis;
    for (QPointF p : arr) {
      x .append(cos (p .x()) * (p .y() + _internal_electrode_radius_no_grade));
      y .append(sin (p .x()) * (p .y() + _internal_electrode_radius_no_grade));
      f .append(p .x());
      if (sin (p .x()) * (p .y() + _internal_electrode_radius_no_grade) > max_y) {
        max_y = sin (p .x()) * (p .y() + _internal_electrode_radius_no_grade);
      }
      if (cos (p .x()) * (p .y() + _internal_electrode_radius_no_grade) > max_x) {
        max_x = cos (p .x()) * (p .y() + _internal_electrode_radius_no_grade);
      }
      if (sin (p .x()) * (p .y() + _internal_electrode_radius_no_grade) < min_y) {
        min_y = sin (p .x()) * (p .y() + _internal_electrode_radius_no_grade);
      }
      if (cos (p .x()) * (p .y() + _internal_electrode_radius_no_grade) < min_x) {
        min_x = cos (p .x()) * (p .y() + _internal_electrode_radius_no_grade);
      }
//      res ._l ._xs [j] .append(res ._eq_p ._xs[j]);
//      res ._l ._ys [j] .append(res ._eq_p ._ys[j]);
      ++j;
    }

    res .max_y = max_y;
    res .max_x = max_x;
    res .min_y = min_y;
    res .min_x = min_x;
    res ._eq_p .append (line);
    ++i;
  }

  return res;
}

double Core::initial_electrone_angle(double i_val) {
  _angle = i_val;

  return _angle;
}

void Core::equipotential_lines_square_coordinates_analitic_solution(int in_steps_radius,
                                                                    int in_steps_angle) {
}

void Core::equipotential_lines_cylinder_coordinates_numeric_solution(int in_steps_radius,
                                                                     int in_steps_angle) {
}

void Core::equipotential_lines_square_coordinates_numeric_solution(int in_steps_radius,
                                                                   int in_steps_angle) {
}
